package mybatis.generator.support.parse;

import mybatis.generator.support.Reflection;
import mybatis.generator.support.StringUtils;
import mybatis.generator.annotation.*;
import org.springframework.core.annotation.AnnotationUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Created by huangdachao on 2018/6/15 16:07.
 */
public class EntityMeta {
    private static final Map<Class<?>, EntityMeta> entityMetaMap = new ConcurrentHashMap<>();
    private Class<?> entityClass;
    private Table table;
    private List<TableColumn> generatedColumns = new ArrayList<>();  // 数据库生成字段
    private List<TableColumn> primaryColumns = new ArrayList<>();    // 主键字段
    private Map<Table, JoinMeta> joinMap = new HashMap<>();          // Table => JoinMeta
    private Map<TableColumn, FieldMeta> fieldMeta = new HashMap<>();
    private Map<String, TableColumn> fieldsMap = new HashMap<>();       // 字段名 => TableColumn
    private Map<String, List<String>> secondaryQueryFieldViewMap = new HashMap<>();

    public static EntityMeta get(Class<?> entityType) {
        EntityMeta meta = entityMetaMap.get(entityType);
        if (meta == null) {
            meta = parse(entityType);
            entityMetaMap.put(entityType, meta);
        }
        return meta;
    }

    private EntityMeta(Class<?> entityClass, String table) {
        this.entityClass = entityClass;
        this.table = new Table(table);
    }

    public TableColumn getGeneratedPrimaryColumn() {
        Set<TableColumn> fields = new HashSet<>(generatedColumns);
        fields.retainAll(primaryColumns);
        if (fields.size() == 1) {
            return fields.iterator().next();
        } else if (fields.size() > 1) {
            throw new IllegalStateException("一个实体类只支持一个自动生成主键字段");
        } else {
            return null;
        }
    }

    public TableColumn getTableColumn(String field) {
        return fieldsMap.get(field);
    }

    public Set<TableColumn> getInsertColumns() {
        return this.fieldMeta.values().stream()
            .filter(f -> f.isInsertable() && f.getTableColumn().toTable().equals(table))
            .map(FieldMeta::getTableColumn)
            .collect(Collectors.toSet());
    }

    public Set<TableColumn> getUpdateColumns() {
        return this.fieldMeta.values().stream()
            .filter(f -> f.isUpdateable() && f.getTableColumn().toTable().equals(table))
            .map(FieldMeta::getTableColumn)
            .collect(Collectors.toSet());
    }

    public Class<?> getEntityClass() {
        return entityClass;
    }

    public Table getTable() {
        return table;
    }

    public List<TableColumn> getGeneratedColumns() {
        return generatedColumns;
    }

    public List<TableColumn> getPrimaryColumns() {
        return primaryColumns;
    }

    public Map<Table, JoinMeta> getJoinMap() {
        return joinMap;
    }

    public Map<TableColumn, FieldMeta> getFieldMeta() {
        return fieldMeta;
    }

    public Map<String, TableColumn> getFieldsMap() {
        return fieldsMap;
    }

    public Map<String, List<String>> getSecondaryQueryFieldViewMap() {
        return secondaryQueryFieldViewMap;
    }

    private static EntityMeta parse(Class<?> entityType) {
        String table = resolveTableName(entityType);
        EntityMeta em = new EntityMeta(entityType, table);
        em.fieldMeta = Reflection.resolveFields(entityType, true).stream()
            .filter(f -> {
                HasOne ho = f.getAnnotation(HasOne.class);
                HasMany hm = f.getAnnotation(HasMany.class);
                Column column = f.getAnnotation(Column.class);
                if ((column != null && (ho != null || hm != null))
                    || (ho != null && hm != null)) {
                    throw new IllegalStateException("@Column、@JoinOne、@JoinMany不可同时使用："
                        + f.getDeclaringClass().getName()
                        + "." + f.getName());
                }

                if (ho != null) {
                    em.secondaryQueryFieldViewMap.put(f.getName(), Arrays.asList(ho.view()));
                    return false;
                } else if (hm != null) {
                    em.secondaryQueryFieldViewMap.put(f.getName(), Arrays.asList(hm.view()));
                    return false;
                }
                return column == null || !column.ignore();
            })
            .map(f -> {
                FieldMeta fm = new FieldMeta(f, table);
                Generated generated = AnnotationUtils.findAnnotation(f, Generated.class);
                Id id = f.getAnnotation(Id.class);
                if (generated != null && generated.value()) {
                    em.generatedColumns.add(fm.getTableColumn());
                }
                if (id != null) {
                    em.primaryColumns.add(fm.getTableColumn());
                }
                em.fieldsMap.put(f.getName(), fm.getTableColumn());
                return fm;
            }).collect(Collectors.toMap(FieldMeta::getTableColumn, Function.identity()));

        Entity en = entityType.getAnnotation(Entity.class);
        if (en != null) {
            for (Join j : en.join()) {
                JoinMeta meta = new JoinMeta(j);
                em.joinMap.put(meta.getTable(), meta);
            }
        }
        return em;
    }

    private static String resolveTableName(Class<?> entityType) {
        Entity en = entityType.getAnnotation(Entity.class);
        if (en != null && !StringUtils.isEmpty(en.table())) {
            return en.table();
        }
        return StringUtils.snakeCase(entityType.getSimpleName());
    }
}
